#include "StaticModel.hpp"

#include "Exception.hpp"
#include <fstream>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/filesystem.hpp>
#include <map>

using namespace std;
using namespace boost;

struct Material
{
	string name;
	vec4 diffuseColor;
};


typedef boost::error_info<struct tag_ObjMtlFilename, Filename>		MaterialFilenameInfo;

map<string, Material> LoadMaterialFile(string filename)
{
	map<string, Material> result;

	ifstream file;
	file.open(filename);
	if(!file.is_open())
	{
		THROW(Exception("Material file could not be loaded: ") << MaterialFilenameInfo(filename));
	}

	Material currentMaterial;

	while(!file.eof())
	{
		string line;
		getline(file, line);

		boost::trim_if(line, is_any_of(" \n\t\v\f\r"));//remove white spaces
		if(line.length()==0 || '#'==line[0])//skip empty lines and comments
			continue;

		vector<string> splitedLine;
		boost::split(splitedLine, line, is_any_of(" "));

		if("newmtl" == splitedLine[0])
		{
			if(!currentMaterial.name.empty())
			{
				result[currentMaterial.name] = currentMaterial;
			}

			currentMaterial = Material();
			currentMaterial.name = splitedLine[1];
		}

		else if("Kd" == splitedLine[0])
		{
			currentMaterial.diffuseColor = vec4(
				lexical_cast<float>(splitedLine[1]),
				lexical_cast<float>(splitedLine[2]),
				lexical_cast<float>(splitedLine[3]),
				1
				);
		}
	}

	// append last material
	if(!currentMaterial.name.empty())
	{
		result[currentMaterial.name] = currentMaterial;
	}

	return result;
}

vector<StaticModel> LoadObjFile(string filename)
{
	try
	{
		vector<StaticModel> result;

		ifstream file;
		file.open(filename);
		if(!file.is_open())
		{
			THROW(Exception("File could not be loaded"));
		}

		map<string, Material> materials;

		StaticModel currentObject;

		vector<vec3> currentPositions;
		vector<vec2> currentTexCoords;
		vector<vec3> currentNormals;

		while(!file.eof())
		{
			string line;
			getline(file, line);

			boost::trim_if(line, is_any_of(" \n\t\v\f\r"));//remove white spaces
			if(line.length()==0 || '#'==line[0])//skip empty lines and comments
				continue;

			vector<string> splitedLine;
			boost::split(splitedLine, line, is_any_of(" "));

			if("mtllib"==splitedLine[0])//load the material lib:
			{
				if(!materials.empty())
					THROW(Exception("MaterialFilename already specified"));
				auto path = boost::filesystem::path(filename).parent_path().string();
				materials = LoadMaterialFile(path.empty() ? splitedLine[1] : path+"/"+splitedLine[1]);
			}

			else if("o"==splitedLine[0])// new object starts
			{
				// append the last loaded object
				if(!currentObject.name.empty())
					result.push_back(currentObject);

				// create a new model
				currentObject = StaticModel();
				currentObject.name = splitedLine[1]; // set the name
			}

			// position
			else if("v"==splitedLine[0])
			{
				vec3 position;
				position.x = lexical_cast<float>(splitedLine[1]);
				position.y = lexical_cast<float>(splitedLine[2]);
				position.z = lexical_cast<float>(splitedLine[3]);
				currentPositions.push_back(position);
			}

			// texCoord
			else if("vt"==splitedLine[0])
			{
				vec2 texCoord;
				texCoord.x = lexical_cast<float>(splitedLine[1]);
				texCoord.y = lexical_cast<float>(splitedLine[2]);
				currentTexCoords.push_back(texCoord);
			}

			// normal
			else if("vn"==splitedLine[0])
			{
				vec3 normal;
				normal.x = lexical_cast<float>(splitedLine[1]);
				normal.y = lexical_cast<float>(splitedLine[2]);
				normal.z = lexical_cast<float>(splitedLine[3]);
				currentNormals.push_back(normal);
			}

			// face
			else if("f"==splitedLine[0])
			{
				if(splitedLine.size() != 4) // "f" and 3 index triples
					THROW(Exception("Object not triangulated"));

				for(unsigned int i = 0; i<3; ++i)
				{
					vector<string> indicies;
					split(indicies, splitedLine[1+i], is_any_of("/"));
					if(indicies.size()!=3)
					{
						THROW(Exception("only vertex/texCoord/normal faces are supported"));
					}
					// Careful! indexing starts with 1, not 0!
					currentObject.positions.push_back(currentPositions[lexical_cast<unsigned int>(indicies[0])-1]);
					if(!indicies[1].empty())// maybe there are no texcoords
						currentObject.texCoords.push_back(currentTexCoords[lexical_cast<unsigned int>(indicies[1])-1]);
					currentObject.normals.push_back(currentNormals[lexical_cast<unsigned int>(indicies[2])-1]);
				}
			}

			else if("usemtl"==splitedLine[0])
			{
				auto material = materials[splitedLine[1]];
				currentObject.diffuseColor = material.diffuseColor;
			}

			else if("s"==splitedLine[0]) //well, we just ignore smooth groups :)
			{
			}

			else
			{
				THROW(Exception("Line could not be parsed: " + line));
			}
		}

		// add the last object
		if(!currentObject.name.empty())
			result.push_back(currentObject);

		return result;
	}
	catch(Exception& ex)
	{
		THROW(ex << FilenameInfo(filename));
	}
}
